
package com.appspot.nnaplanner;

import com.appspot.nnaplanner.DAL.DAL;
import com.appspot.nnaplanner.DAL.DALConnection;
import com.appspot.nnaplanner.DAL.DALStop;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author david
 */
public class Utils {

    /**
     * Generates a list of key words based onthe given string. useless words
     * like, an on a, etc are removed
     * For example "the bristol computer on science" will result in
     * bristol, computer, science, bristol computer, bristol science,
     * computer bristol, computer science, science bristol, science computer
     * @param words The string you want to convert to key words
     * @return A list of key words
     */
    public static List<String> getKeywords(String words) {
        String[] keywords = words.split(" ");
        List<String> result = new ArrayList<String>();
        for (int i = 0; i < keywords.length; i++) {
            if (meaningful(keywords[i].toLowerCase())) {
                result.add(keywords[i].toLowerCase());
            }
        }
        if (result.size() > 1) {
            List<String> twowords = new ArrayList<String>();
            for (int i = 0; i < result.size(); i++) {
                for (int j = 0; j < result.size(); j++) {
                    if (i != j) {
                        twowords.add(result.get(i) + " " + result.get(j));
                    }
                }
            }
            result.addAll(twowords);
        }
        return result;
    }

    /**
     * Is the word passed of any meaning? not in the list of words below
     * @param word The word you want to check
     * @return True if it is a meaningful word. Not like the/a/an
     */
    private static boolean meaningful(String word) {
        String[] useless = {"an", "on", "the", "and", "a"};
        for (String u : useless) {
            if (word.equals(u)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Determines if two times are with in a given range of each other
     * @param first The first time
     * @param second The seond time
     * @param range The range in minutes the times should be in
     * @return
     */
    public static boolean TimeInRange(Date first, Date second, int range) {
        long diff = 0l;
        if (first.compareTo(second) >= 0) {
            diff = first.getTime() - second.getTime();
        } else {
            diff = second.getTime() - first.getTime();
        }
        long minutes = range * 60 * 1000;
        //System.out.println("Diff: " + diff + " min:" + minutes);
        return diff <= minutes;
    }

    /**
     * Gets stops near a given stop. An offset of 0.004 gets stops about
     * 500 meters fromt he given stop
     * @param ds The stop to search around
     * @param offset the points from the current location
     * @return a list of stops near a given stop
     */
    public static List<DALStop> getStopsNear(DALStop ds, float offset) {
        return getStopsNear(ds.getLatitude(), ds.getLongitude(), offset);
    }

    /**
     * Gets stops neat the given point. An offset of 0.004 gets stops about
     * 500 meters fromt he given stop
     * @param lon the longitude
     * @param lat the latitude
     * @param offset the points from the current location
     * @return a list of stops near the latitude and longitude given
     */
    public static List<DALStop> getStopsNear(float lat, float lon, float offset) {
        javax.jdo.Query q = DAL.newQuery();
        q.setClass(DALStop.class);
        q.setFilter("_longitude > minLong && _longitude < maxLong");
        q.declareParameters("Float minLong, Float maxLong");
        List<DALStop> latitudes = (List<DALStop>) q.execute(lon - offset, lon + offset);
        q = DAL.newQuery();
        q.setClass(DALStop.class);
        q.setFilter("_latitude > minLat && _latitude < maxLat");
        q.declareParameters("Float minLat, Float maxLat");
        List<DALStop> longitudes = (List<DALStop>) q.execute(lat - offset, lat + offset);
        /*
        System.out.println("Getting Stops near lat: " + lat + " lon:" + lon + " offset:" + offset);
        System.out.println("latitudes found: "  + latitudes.size());
        System.out.println("longitudes found: "  + longitudes.size());
         */
        ArrayList<DALStop> results = new ArrayList<DALStop>();
        ArrayList<DALStop> results2 = new ArrayList<DALStop>();
        results.addAll(longitudes);
        results2.addAll(latitudes);
        results.retainAll(results2);
        q.closeAll();
        return results;
    }

    /**
     * Returns the number of minutes it takes to walk between two stops
     * @param first
     * @param second
     * @return The minutes it takes to walk between the two stops
     */
    public static int getTimeToWalkBetween(DALStop first, DALStop second) {
        Double dist = getDistanceBetween(first, second);
        dist = dist * 1000;
        int time = getTimeToWalk(dist.intValue());
        return time;
    }

    /**
     * Determines how long it takes to walk the given meters.
     * Assuming it takes about 2 minutes to stroll 100 meters (traffic lights, crossings, etc...)
     * @param meters the meters you are going to walk
     * @return the time in minutes it takes to walk x meters
     */
    public static int getTimeToWalk(int meters) {
        Double duration = meters * 0.02;
        int result = duration.intValue();
        return result;
    }

    /**
     * Returns how many meters there are between two given Stops
     * @param first The first stop
     * @param second The second stop
     * @return The distance in kilometers between the stops
     */
    public static double getDistanceBetween(DALStop first, DALStop second) {
        return getDistanceBetween(first.getLatitude(), first.getLongitude(), second.getLatitude(), second.getLongitude());
    }

    /**
     * Gets an optimized route, buy removed the walking connections from the start and end of a route
     * @param route the route you want to optimize
     * @param time the time you need to route to be optimized to
     * @param minutes the number of minutes the user is prepared to wait for a connection
     * @return teh optimized route
     */
    public static List<DALConnection> getOptimizedRoute(List<DALConnection> route, Date time, int minutes) {
        List<DALConnection> conns = new ArrayList<DALConnection>();
        //System.out.println();
        DateFormat df = new SimpleDateFormat("HH:mm");
        List<DALConnection> walkers = new ArrayList<DALConnection>();

        for (DALConnection dc : route) {
            DALConnection d = dc.getByID(dc.getID());
            Date firstTime = null;
            try {
                firstTime = df.parse("00:00");
            } catch (ParseException ex) {
                Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
            }

            boolean savedFirstDate = false;
            int duration = d.getDurations().get(0);
            List<Date> timesInRange = new ArrayList<Date>();
            for (int i = 0; i < d.getTimes().size(); i++) {
                String info = d.getDescriptions().get(i);
                Date deptartTime = d.getTimes().get(i);
                if (info.toLowerCase().equals("walk!")) {
                    //walkable connection start time is Irrelivant
                    //we only want walkable connections after
                    walkers.add(d);
                    deptartTime.setTime(1 + deptartTime.getTime());
                    
                }

                if (Utils.TimeInRange(deptartTime, time, minutes)) {
                    timesInRange.add(deptartTime);
                    if (!savedFirstDate) {
                        firstTime = time;
                    }
                }

            }
            d.setTimes(timesInRange);
            conns.add(d);

            if (d.getTimes().size() > 0) {
                time.setTime(firstTime.getTime() + minutes + duration * 1000 * 60);
            }
            savedFirstDate = false;
        }

        // Remove walkiing ellements from start
        int prev = 0;
        int index = 0;
        for (int i = 0; i < walkers.size(); i++) {
            if (walkers.get(i).getTimes().size() == 0) {
                //we have a walking route, that does not have departure time
                index = route.indexOf(walkers.get(i));
                if (index == prev) {
                    conns.remove(walkers.get(i));
                }
                prev++;

            }

        }
        // Remove walkiing ellements from end of route
        prev = route.size() - 1;
        for (int i = walkers.size() - 1; i >= 0; i--) {
            if (walkers.get(i).getTimes().size() == 0) {
                //we have a walking route, that does not have departure time
                index = route.indexOf(walkers.get(i));
                if (index == prev) {
                    conns.remove(walkers.get(i));
                }
                prev--;

            }

        }
        return conns;

    }

    /**
     * Returns how many meters there are between two given points.
     * @param lat1 the latitude of the first point
     * @param lon1 the longitude of the first point
     * @param lat2 the latitude of the second point
     * @param lon2 the longitude of the second point
     * @return the distance in kilometers between the points
     */
    public static double getDistanceBetween(double lat1, double lon1, double lat2, double lon2) {
        //http://en.wikipedia.org/wiki/Haversine_formula
        double radius = 6371;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                Math.sin(dLng / 2) * Math.sin(dLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        Double result = radius * c;
        return result;
    }

    /**
     * Generates all the walking connections for this stop, and adds these to the graph
     * @param ds the stop you want to generate connections for
     */
    public static void generateConnections(DALStop ds) {
        ArrayList<DALConnection> walks = new ArrayList<DALConnection>();
        List<DALStop> stopsNear = Utils.getStopsNear(ds, 0.004f);
        for (DALStop s : stopsNear) {
            if (!s.equals(ds)) {
                int duration = Utils.getTimeToWalkBetween(ds, s);

                if (duration < 2) {
                    duration = 2;
                }

                ds.setConnected(true);
                ds.setConnectionCount(1 + ds.getConnectionCount());
                DALConnection there = new DALConnection();
                DALConnection back = new DALConnection();
                there.setStop(ds);
                there.setDestination(s);
                there.setKeywords(ds.getKeywords());
                there.getKeywords().add(there.getDestinationID());
                there.setID(ds.getID() + s.getID());
                back.setStop(s);
                back.setDestination(ds);
                back.setKeywords(ds.getKeywords());
                back.getKeywords().add(back.getID());
                back.getKeywords().add(back.getStopID());
                back.getKeywords().add(back.getDestinationID());
                there.setID(s.getID() + ds.getID());
                java.text.DateFormat df = new java.text.SimpleDateFormat("HH:mm");
                java.util.Date d = new Date(0);
                try {
                    d = df.parse("00:00");
                } catch (Exception e) {
                    Logger.getAnonymousLogger().warning("can not parse date format\n" + e.getMessage());
                }
                back.addDeparture(d, duration, "Walk!");
                there.addDeparture(d, duration, "Walk!");

                if (!walks.contains(there)) {
                    walks.add(there);
                }

                if (!walks.contains(back)) {
                    walks.add(back);
                }

            }
        }
        ds.save();

        NNappData.getGraph().addVertex(ds.getID());

        for (DALConnection d : walks) {
            float duration = d.getDurations().get(0);
            d.setDepartures(d.getTimes().size());
            NNappData.getGraph().addEdge(d.getStopID(), d.getDestinationID());
            NNappData.getGraph().setEdgeWeight(NNappData.getGraph().getEdge(d.getStopID(), d.getDestinationID()), duration);
            d.save();
        }

    }
}
